package com.csx.orm.mybatisplus.handler;

import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Objects;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

/**
 * <p> what does this class do ? </p>
 *   自定义的枚举类型的数据处理器。 该自定义枚举类型的数据处理器主要负责处理枚举类型的参数值的设置。
 *   除了枚举类型，我们可以在 插入数据库 或者 从数据局查询数据后并在返给给调用者之前 自定义各种类型数据值的设置。
 *   应用:
 *      特殊类型 -> 数据库特殊类型的映射
 *
 *   <p>可以通过 配置 mybatis-plus.configuration.default-enum-type-handler=com.csx.orm.mybatisplus.handler.MyCustomEnumTypeHandler使得该类生效</p>
 * <p> How does it work ?</p>
 * <p> Why does it do this ?</p>
 *
 * @author cuisongxu
 * @date 2023/11/6 周一 11:11
 */
public class MyCustomEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {

    private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
    private final Class<E> enumClassType;
    private final Class<?> propertyType;
    private final Invoker getInvoker;

    public MyCustomEnumTypeHandler(Class<E> enumClassType) {
        if (enumClassType == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        } else {
            this.enumClassType = enumClassType;
            MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY);
            String name = "code";
            this.propertyType = ReflectionKit.resolvePrimitiveIfNecessary(metaClass.getGetterType(name));
            this.getInvoker = metaClass.getGetInvoker(name);
        }
    }

    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        if (jdbcType == null) {
            ps.setObject(i, this.getValue(parameter));
        } else {
            ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
        }

    }

    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Object value = rs.getObject(columnName, this.propertyType);
        return null == value && rs.wasNull() ? null : this.valueOf(value);
    }

    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Object value = rs.getObject(columnIndex, this.propertyType);
        return null == value && rs.wasNull() ? null : this.valueOf(value);
    }

    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Object value = cs.getObject(columnIndex, this.propertyType);
        return null == value && cs.wasNull() ? null : this.valueOf(value);
    }

    private Object getValue(Object object) {
        try {
            return this.getInvoker.invoke(object, new Object[0]);
        } catch (ReflectiveOperationException var3) {
            throw ExceptionUtils.mpe(var3);
        }
    }

    private E valueOf(Object value) {
        E[] es = (E[]) this.enumClassType.getEnumConstants();
        return (E) Arrays.stream(es).filter((e) -> this.equalsValue(value, this.getValue(e))).findAny().orElse((E) null);
    }

    protected boolean equalsValue(Object sourceValue, Object targetValue) {
        String sValue = StringUtils.toStringTrim(sourceValue);
        String tValue = StringUtils.toStringTrim(targetValue);
        return sourceValue instanceof Number && targetValue instanceof Number && (new BigDecimal(sValue)).compareTo(new BigDecimal(tValue)) == 0 ? true : Objects.equals(sValue, tValue);
    }
}
